/*  
 *  Copyright 2006 Paul Jack.
 *
 *  This file is part of the Dex compiler suite. 
 *  
 *  Dex is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 2 of the License, or (at your
 *  option) any later version.
 *  
 *  Dex is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 *  Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */
package dex.misc;


import java.util.AbstractSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;


/**
 * A cache backed by a map.
 *
 * @param <T>  the type of the cache
 */
public class MapCache<T> extends AbstractSet<T> implements Cache<T> {

	
	/**
	 * The map containing cached elements.
	 */
	private Map<T,T> map;
	

	/**
	 * Constructs a cache backed by a HashMap.
	 */
	public MapCache() {
		this(new HashMap<T,T>());
	}


	/**
	 * Constructs a new cache backed by the given map.
	 * The given map must be empty.
	 * 
	 * @param map  the map to store cached elements
	 */
	public MapCache(Map<T,T> map) {
		if (map == null) {
			throw new IllegalArgumentException("map may not be null");
		}
		if (!map.isEmpty()) {
			throw new IllegalArgumentException("map may not be null");			
		}
		this.map = map;
	}
	
	
	@SuppressWarnings("unchecked")
	public <K extends T> K cache(K element) {
		if (element == null) {
			throw new IllegalArgumentException("element may not be null.");
		}
		K result = (K)map.get(element);
		if (result == null) {
			map.put(element, element);
			return element;
		} else {
			return result;
		}
	}


	/**
	 * Adds the element to the cache.
	 * 
	 * @param element  the element to cache
	 * @return  false if the element is new; true if the element already existed
	 */
	@Override
	public boolean add(T element) {
		T result = cache(element);
		return result != element;
	}


	/**
	 * Clears the cache.
	 */
	@Override
	public void clear() {
		map.clear();
	}


	/**
	 * Tests if the given object exists in the cache.
	 * 
	 * @param   element   the element to test
	 * @return  true if that element exists in the cache
	 */
	@Override
	public boolean contains(Object element) {
		return map.containsKey(element);
	}

	
	/**
	 * Tests all of the given objects to see if they exist in the cache.
	 *
	 * @param   c    the collection to test
	 * @return  true  if this cache contains all those elements
	 */
	@Override
	public boolean containsAll(Collection<?> c) {
		return map.keySet().containsAll(c);
	}


	/**
	 * Returns true if this cache is empty.
	 * 
	 * @return  true if this cache is empty
	 */
	@Override
	public boolean isEmpty() {
		return map.isEmpty();
	}


	/**
	 * Returns an iterator over this cache's elements.
	 * 
	 * @return  an iterator over this cache's elements
	 */
	public Iterator<T> iterator() {
		return map.keySet().iterator();
	}


	/**
	 * Removes the given element from the cache.
	 * 
	 * @param  element   the element to remove
	 * @return   true if the element existed, false otherwise
	 */
	public boolean remove(Object element) {
		T r = map.remove(element);
		return (r != null);
	}


	/**
	 * Returns the size of this cache.
	 * 
	 * @return  the size of this cache
	 */
	public int size() {
		return map.size();
	}


	/**
	 * Returns a new array containing this cache's elements
	 * 
	 * @return  a new array containing this cache's elements
	 */
	public Object[] toArray() {
		return map.keySet().toArray();
	}

	
	/**
	 * Returns an array containing this cache's elements.
	 * 
	 * @param  array  the array to fill
	 * @return  that array, or a new array if it wasn't big enough
	 */
	public <K> K[] toArray(K[] array) {
		return map.keySet().toArray(array);
	}


	/**
	 * Removes all of the given elements from the cache.
	 * 
	 * @param c  the elements to remove
	 * @return  true if any of those elements were in the cache
	 */
	public boolean removeAll(Collection<?> c) {
		return map.keySet().removeAll(c);
	}
	
	
	/**
	 * Removes everything except the specified elements from the cache.
	 * 
	 * @param c  the elements to retain
	 * @return  true if any elements were deleted, false otherwise
	 */
	public boolean retainAll(Collection<?> c) {
		return map.keySet().retainAll(c);
	}

}
